In [1]:
import numpy as np
import pandas as pd
import os,sys
from pathlib import Path
import matplotlib.pyplot as plt
import plotly.express as px

import PV_ICE

cwd = os.getcwd() #grabs current working directory

testfolder = str(Path().resolve().parent.parent / 'PV_ICE' / 'TEMP' / 'MESC-NRELStdScens')
inputfolder = str(Path().resolve().parent.parent / 'PV_ICE' / 'baselines'/'NRELStdScenarios')
baselinesfolder = str(Path().resolve().parent.parent /'PV_ICE' / 'baselines')
supportMatfolder = str(Path().resolve().parent.parent / 'PV_ICE' / 'baselines' / 'SupportingMaterial')
#altBaselinesfolder = str(Path().resolve().parent.parent / 'PV_ICE' / 'baselines' / 'Energy_CellModuleTechCompare')

if not os.path.exists(testfolder):
    os.makedirs(testfolder)
In [2]:
print("Python version ", sys.version)
print("Pandas version ", pd.__version__)
print("pyplot ", plt.matplotlib.__version__)
print("PV_ICE version ", PV_ICE.__version__)
Python version  3.11.5 | packaged by Anaconda, Inc. | (main, Sep 11 2023, 13:26:23) [MSC v.1916 64 bit (AMD64)]
Pandas version  2.0.3
pyplot  3.7.2
PV_ICE version  

Bring in NREL Standard Scenarios data for projection¶

https://scenarioviewer.nrel.gov/ download the data, save it to the NRELStdScenarios folder in baselines.

ReEDS capacity data is cumulative, and only reports even years, so we need to data munge.

In [3]:
stdsceninput_raw = pd.read_csv(os.path.join(inputfolder, 'StdScen23_Mid_Case_annual_national.csv'),
           skiprows=[0,1,2,4], header=[0], index_col=1)
#other scenario options:
#StdScen23_Mid_Case_NoNascent_annual_national.csv
In [4]:
stdscen_pv_evens = stdsceninput_raw.filter(like='(MW)').filter(like='PV')
In [5]:
#take the difference betwen even years to get annual additions from cumulative
stdscens_evens_added_cap = stdscen_pv_evens.diff()
#divide by 2 to evenly distribute across odd and even years
stdscens_added_cap = stdscens_evens_added_cap/2
In [6]:
#now make previous odds = next year evens deployment
idx_temp = pd.RangeIndex(start=2024,stop=2051,step=1) #create the index
stdscens_added_cap_filled = stdscens_added_cap.reindex(idx_temp, method='bfill')
stdscens_added_cap_filled
Out[6]:
Behind-the-meter PV capacity (MW) Utility-scale PV capacity (MW)
2024 NaN NaN
2025 6535.55 20784.15
2026 6535.55 20784.15
2027 8883.10 23685.35
2028 8883.10 23685.35
2029 12385.30 9459.10
2030 12385.30 9459.10
2031 9558.50 19190.80
2032 9558.50 19190.80
2033 6421.50 42129.65
2034 6421.50 42129.65
2035 2684.05 53625.70
2036 2684.05 53625.70
2037 2488.95 29024.65
2038 2488.95 29024.65
2039 2407.60 32607.90
2040 2407.60 32607.90
2041 2478.40 36283.55
2042 2478.40 36283.55
2043 2807.05 25049.15
2044 2807.05 25049.15
2045 2781.95 34141.90
2046 2781.95 34141.90
2047 3182.55 30891.35
2048 3182.55 30891.35
2049 3566.90 29768.90
2050 3566.90 29768.90
In [7]:
#Reeds is in MWac, we're in MWdc, so multiply residential by 1.1 and utility by 1.3
stdscens_pv_filled_dc = pd.DataFrame()
stdscens_pv_filled_dc['Behind-the-meter PV capacity (MWdc)'] = stdscens_added_cap_filled['Behind-the-meter PV capacity (MW)']*1.1
stdscens_pv_filled_dc['Utility-scale PV capacity (MWdc)'] = stdscens_added_cap_filled['Utility-scale PV capacity (MW)']*1.3
stdscens_pv_filled_dc
Out[7]:
Behind-the-meter PV capacity (MWdc) Utility-scale PV capacity (MWdc)
2024 NaN NaN
2025 7189.105 27019.395
2026 7189.105 27019.395
2027 9771.410 30790.955
2028 9771.410 30790.955
2029 13623.830 12296.830
2030 13623.830 12296.830
2031 10514.350 24948.040
2032 10514.350 24948.040
2033 7063.650 54768.545
2034 7063.650 54768.545
2035 2952.455 69713.410
2036 2952.455 69713.410
2037 2737.845 37732.045
2038 2737.845 37732.045
2039 2648.360 42390.270
2040 2648.360 42390.270
2041 2726.240 47168.615
2042 2726.240 47168.615
2043 3087.755 32563.895
2044 3087.755 32563.895
2045 3060.145 44384.470
2046 3060.145 44384.470
2047 3500.805 40158.755
2048 3500.805 40158.755
2049 3923.590 38699.570
2050 3923.590 38699.570
In [8]:
plt.plot(stdscens_pv_filled_dc)
plt.legend(stdscens_pv_filled_dc.columns)
plt.ylabel('Annual Installed Capacity\n[MWdc]')
Out[8]:
Text(0, 0.5, 'Annual Installed Capacity\n[MWdc]')

Set up PV ICE simulation using historical US installs with the NREL Std Scenarios projection¶

In [9]:
#c-Si
MATERIALS = ['glass','aluminium_frames','silver','silicon', 'copper', 'encapsulant', 'backsheet']
moduleFile = os.path.join(baselinesfolder, 'baseline_modules_mass_US_updatedT50T90.csv')
#CdTe
MATERIALS_CdTe = ['glass_cdte','aluminium_frames_cdte', 'copper_cdte', 'encapsulant_cdte','cadmium','tellurium']
moduleFile_CdTe = os.path.join(baselinesfolder, 'baseline_modules_mass_US_CdTe.csv')
In [10]:
sim1 = PV_ICE.Simulation(name='MESC_StdScen', path=testfolder)
scens = ['23_MidCase_cSi', '23_MidCase_CdTe']

#c-Si
sim1.createScenario(name='23_MidCase_cSi', massmodulefile=moduleFile) #create the scenario, name and mod file attach
for mat in MATERIALS:
    materialfile = os.path.join(baselinesfolder, 'baseline_material_mass_'+str(mat)+'.csv')
    sim1.scenario['23_MidCase_cSi'].addMaterial(mat, massmatfile=materialfile) # add all materials listed in MATERIALS
#CdTe
sim1.createScenario(name='23_MidCase_CdTe', massmodulefile=moduleFile_CdTe) #create the scenario, name and mod file attach
for mat in MATERIALS_CdTe:
    materialfile = os.path.join(baselinesfolder, 'baseline_material_mass_'+str(mat)+'.csv')
    sim1.scenario['23_MidCase_CdTe'].addMaterial(mat, massmatfile=materialfile) # add all materials listed in MATERIALS_cdte
path = C:\Users\hmirletz\Documents\GitHub\PV_ICE\PV_ICE\TEMP\MESC-NRELStdScens
Baseline folder directed to default:  C:\Users\hmirletz\Documents\GitHub\PV_ICE\PV_ICE\baselines
No energy module file passed. If desired, pass one of the following options:  ['baseline_modules_energy.csv', 'baseline_modules_energy_CdTe.csv']
No energy module file passed. If desired, pass one of the following options:  ['baseline_modules_energy.csv', 'baseline_modules_energy_CdTe.csv']

For future deployment in the US of c-Si and CdTe, the assumptions are:

  • all Residential will be c-Si
  • CdTe will max out at 22 GW in 2030, and c-Si will make up the remaining demand to fullfill Utility deployment

We will linearly interpolate between historical CdTe Deployment and the 22 GW in 2030.

In [11]:
plt.plot(sim1.scenario['23_MidCase_CdTe'].dataIn_m.loc[:(2024-1995),['year','new_Installed_Capacity_[MW]']])
Out[11]:
[<matplotlib.lines.Line2D at 0x26ff5ac85d0>,
 <matplotlib.lines.Line2D at 0x26ff5af3390>]
In [12]:
#linearly interpolate CdTe
#estimated 2024 install = 14GW
idx_temp = pd.RangeIndex(start=2024,stop=2051,step=1) #create the index
CdTeRamp = pd.DataFrame(index=idx_temp, columns=['CdTe_deploy_[MWdc]'], dtype=float)
CdTeRamp.loc[2024] = 14000
CdTeRamp.loc[2030] = 22000
CdTeRamp_full = round(CdTeRamp.interpolate(),0)
#CdTeRamp_full
In [13]:
#Modify the CdTe Scenario deployment schedule
sim1.modifyScenario(scenarios='23_MidCase_CdTe',stage='new_Installed_Capacity_[MW]', 
                    value=CdTeRamp_full.sum(axis=1), start_year=2024) #
In [14]:
plt.plot(sim1.scenario['23_MidCase_CdTe'].dataIn_m.loc[:,'new_Installed_Capacity_[MW]'])
plt.ylabel('Annual Installed Capacity\n[MWdc]')
Out[14]:
Text(0, 0.5, 'Annual Installed Capacity\n[MWdc]')
In [15]:
#now create the silicon deployment by subtracting CdTe from the total Utility deployment, add resi
mescdeploybytech = pd.DataFrame()
utilitydeploytotal = stdscens_pv_filled_dc['Utility-scale PV capacity (MWdc)'].values
CdTedeployutility = sim1.scenario['23_MidCase_CdTe'].dataIn_m.loc[(2024-1995):,'new_Installed_Capacity_[MW]'].values
resi = stdscens_pv_filled_dc['Behind-the-meter PV capacity (MWdc)'].values
mescdeploybytech['cSi_[MWdc]'] = utilitydeploytotal-CdTedeployutility+resi
mescdeploybytech.iloc[0,0]=14000 #fix nan issue
mescdeploybytech['CdTe_[MWdc]'] = sim1.scenario['23_MidCase_CdTe'].dataIn_m.loc[(2024-1995):,'new_Installed_Capacity_[MW]'].values
mescdeploybytech.index = idx_temp
In [16]:
plt.plot(mescdeploybytech)
plt.legend(mescdeploybytech.columns)
plt.ylabel('Annual Installed Capacity\n[MWdc]')
Out[16]:
Text(0, 0.5, 'Annual Installed Capacity\n[MWdc]')
In [17]:
#Modify the c-Si deployment schedule
sim1.modifyScenario(scenarios=['23_MidCase_cSi'],stage='new_Installed_Capacity_[MW]', 
                    value=mescdeploybytech['cSi_[MWdc]'], start_year=2024) #
In [18]:
yeargraph = sim1.scenario['23_MidCase_cSi'].dataIn_m.loc[:,'year']
plt.plot(yeargraph,sim1.scenario['23_MidCase_cSi'].dataIn_m.loc[:,'new_Installed_Capacity_[MW]'], label='cSi')
plt.plot(yeargraph,sim1.scenario['23_MidCase_CdTe'].dataIn_m.loc[:,'new_Installed_Capacity_[MW]'], label='CdTe')
plt.legend()
plt.ylim(0,)
plt.xlim(1995,2050)
plt.ylabel('Annual Installed Capacity\n[MWdc]')
Out[18]:
Text(0, 0.5, 'Annual Installed Capacity\n[MWdc]')

Create High recycling scenario¶

Assume high recycling = Solar Cycle ideal (Dias 2022, Renewable and Sustainable Energy Reviews)

  • modules, 75% sent to recycling
  • glass, 100% closed loop
  • aluminium frames, 100% closed loop
  • silver, 100% closed loop
  • copper, 100% closed loop
  • silicon, 100% LQ recycling

We don't need to create a high CdTe recycling scenario, already set to 100% collect and recycle.

In [19]:
sim1.createScenario(name='23_MidCase_cSi_hiR', massmodulefile=moduleFile)
for mat in MATERIALS:
    materialfile = os.path.join(baselinesfolder, 'baseline_material_mass_'+str(mat)+'.csv')
    sim1.scenario['23_MidCase_cSi_hiR'].addMaterial(mat, massmatfile=materialfile) # add all materials listed in MATERIALS

#modify deployment curve as before
sim1.modifyScenario(scenarios=['23_MidCase_cSi_hiR'],stage='new_Installed_Capacity_[MW]', 
                    value=mescdeploybytech['cSi_[MWdc]'], start_year=2024) #
#modify EoL recycling variables
#module
sim1.modifyScenario(scenarios=['23_MidCase_cSi_hiR'],stage='mod_EOL_collection_eff', value=75, start_year=2024) #collect 75%
sim1.modifyScenario(scenarios=['23_MidCase_cSi_hiR'],stage='mod_EOL_pg4_recycled', value=100, start_year=2024) #recycle all collected
sim1.modifyScenario(scenarios=['23_MidCase_cSi_hiR'],stage='mod_EOL_pb4_recycled', value=100, start_year=2024) #

#material, all become a recycling target, and send to high quality
sim1.scenario['23_MidCase_cSi_hiR'].modifyMaterials('glass', 'mat_PG4_Recycling_target', 100, start_year=2024)
sim1.scenario['23_MidCase_cSi_hiR'].modifyMaterials('glass', 'mat_EOL_Recycled_into_HQ', 100, start_year=2024)

sim1.scenario['23_MidCase_cSi_hiR'].modifyMaterials('aluminium_frames', 'mat_PG4_Recycling_target', 100, start_year=2024)
sim1.scenario['23_MidCase_cSi_hiR'].modifyMaterials('aluminium_frames', 'mat_EOL_Recycled_into_HQ', 100, start_year=2024)

sim1.scenario['23_MidCase_cSi_hiR'].modifyMaterials('silver', 'mat_PG4_Recycling_target', 100, start_year=2024)
sim1.scenario['23_MidCase_cSi_hiR'].modifyMaterials('silver', 'mat_EOL_Recycled_into_HQ', 100, start_year=2024)

sim1.scenario['23_MidCase_cSi_hiR'].modifyMaterials('copper', 'mat_PG4_Recycling_target', 100, start_year=2024)
sim1.scenario['23_MidCase_cSi_hiR'].modifyMaterials('copper', 'mat_EOL_Recycled_into_HQ', 100, start_year=2024)

sim1.scenario['23_MidCase_cSi_hiR'].modifyMaterials('silicon', 'mat_PG4_Recycling_target', 100, start_year=2024)
sim1.scenario['23_MidCase_cSi_hiR'].modifyMaterials('silicon', 'mat_EOL_Recycled_into_HQ', 100, start_year=2024)
No energy module file passed. If desired, pass one of the following options:  ['baseline_modules_energy.csv', 'baseline_modules_energy_CdTe.csv']
In [31]:
#sim1.scenario['23_MidCase_CdTe'].dataIn_m['mod_EOL_collection_eff']
#sim1.scenario['23_MidCase_cSi'].dataIn_m['mod_EOL_collection_eff']
#sim1.scenario['23_MidCase_cSi_hiR'].material['glass'].matdataIn_m.keys()
In [ ]:
 

Run the scenarios¶

In [21]:
sim1.calculateMassFlow()
>>>> Calculating Material Flows <<<<

Working on Scenario:  23_MidCase_cSi
********************
Finished Area+Power Generation Calculations
==> Working on Material :  glass
==> Working on Material :  aluminium_frames
==> Working on Material :  silver
==> Working on Material :  silicon
==> Working on Material :  copper
==> Working on Material :  encapsulant
==> Working on Material :  backsheet
Working on Scenario:  23_MidCase_CdTe
********************
Finished Area+Power Generation Calculations
==> Working on Material :  glass_cdte
==> Working on Material :  aluminium_frames_cdte
Recycled surplus End of Sim for Mat  aluminium_frames_cdte  Scenario  23_MidCase_CdTe  =  14469.188118420869  tonnes.
==> Working on Material :  copper_cdte
==> Working on Material :  encapsulant_cdte
==> Working on Material :  cadmium
==> Working on Material :  tellurium
C:\Users\hmirletz\Documents\GitHub\PV_ICE\PV_ICE\main.py:1427: RuntimeWarning: invalid value encountered in scalar subtract
  recycledsurplus = ( dm['mat_MFG_Recycled_HQ_into_MFG'].loc[rr] + #mfg scrap in that year
Working on Scenario:  23_MidCase_cSi_hiR
********************
Finished Area+Power Generation Calculations
Warning: Paths 0 through 4 add to above 100%;Fixing by Updating Landfill value to the remainder of100-(P0+P2+P3+P4).
Warning: Paths B 1 through 4 add to above 100%;Fixing by Updating Landfill value to the remainder of100-(P2+P3+P4).
==> Working on Material :  glass
==> Working on Material :  aluminium_frames
==> Working on Material :  silver
==> Working on Material :  silicon
==> Working on Material :  copper
==> Working on Material :  encapsulant
==> Working on Material :  backsheet
In [22]:
ii_yearly, ii_cumu = sim1.aggregateResults()
In [23]:
sim1.plotMetricResults()
["MESC', 'StdScen', '23', 'MidCase', 'cSi", "MESC', 'StdScen', '23', 'MidCase', 'CdTe", "MESC', 'StdScen', '23', 'MidCase', 'cSi', 'hiR"]
In [24]:
recycled_by_mat = pd.DataFrame()
for mats in MATERIALS:
    recycled_by_mat[str(mats)] = sim1.scenario['23_MidCase_cSi'].material[mats].matdataOut_m['mat_EOL_Recycled_2_HQ']
for mats in MATERIALS:
    recycled_by_mat[str(mats+'_hiR')] = sim1.scenario['23_MidCase_cSi_hiR'].material[mats].matdataOut_m['mat_EOL_Recycled_2_HQ']
for mats_cdte in MATERIALS_CdTe:
    recycled_by_mat[str(mats_cdte)] = sim1.scenario['23_MidCase_CdTe'].material[mats_cdte].matdataOut_m['mat_EOL_Recycled_2_HQ']
recycled_by_mat.index = pd.RangeIndex(start=1995,stop=2051,step=1)
recycled_by_mat_tonnes = recycled_by_mat/1000000  # This is the ratio for grams to Metric tonnes
#SUM and Annual 2024-2030, 
In [25]:
recycled_by_mat_tonnes.loc[2024:2030,]
Out[25]:
glass aluminium_frames silver silicon copper encapsulant backsheet glass_hiR aluminium_frames_hiR silver_hiR silicon_hiR copper_hiR encapsulant_hiR backsheet_hiR glass_cdte aluminium_frames_cdte copper_cdte encapsulant_cdte cadmium tellurium
2024 19.170014 41.500447 0.0 0.0 0.0 0.0 0.0 1775.001253 518.755591 18.456992 297.762340 2.830843 0.0 0.0 66.045262 0.004125 1.643801 0.0 0.186428 0.200481
2025 20.347898 41.391789 0.0 0.0 0.0 0.0 0.0 1884.064635 517.397360 17.851945 262.735362 3.003694 0.0 0.0 101.931271 0.013053 2.537586 0.0 0.283476 0.304844
2026 22.019460 44.069153 0.0 0.0 0.0 0.0 0.0 2038.838870 550.864410 17.390710 232.389187 3.248966 0.0 0.0 153.969708 0.035230 3.834151 0.0 0.422172 0.453994
2027 7.799168 14.955748 0.0 0.0 0.0 0.0 0.0 722.145220 186.946851 3.192390 75.928343 1.145072 0.0 0.0 227.898120 0.084672 5.676870 0.0 0.616539 0.663012
2028 65.970255 130.824521 0.0 0.0 0.0 0.0 0.0 6108.356991 1635.306514 50.463798 669.531092 9.738106 0.0 0.0 330.863692 0.186636 8.244510 0.0 0.883813 0.950432
2029 67.169584 131.356918 0.0 0.0 0.0 0.0 0.0 6219.405893 1641.961472 45.872569 650.814490 9.910815 0.0 0.0 471.541081 0.385350 11.754295 0.0 1.244634 1.338451
2030 90.021086 174.120358 0.0 0.0 0.0 0.0 0.0 8335.285768 2176.504475 56.420174 872.170952 13.281751 0.0 0.0 660.172328 0.756794 16.463090 0.0 1.723065 1.852947
In [26]:
recycled_by_mat_tonnes.loc[2024:2030,'glass':'backsheet']
Out[26]:
glass aluminium_frames silver silicon copper encapsulant backsheet
2024 19.170014 41.500447 0.0 0.0 0.0 0.0 0.0
2025 20.347898 41.391789 0.0 0.0 0.0 0.0 0.0
2026 22.019460 44.069153 0.0 0.0 0.0 0.0 0.0
2027 7.799168 14.955748 0.0 0.0 0.0 0.0 0.0
2028 65.970255 130.824521 0.0 0.0 0.0 0.0 0.0
2029 67.169584 131.356918 0.0 0.0 0.0 0.0 0.0
2030 90.021086 174.120358 0.0 0.0 0.0 0.0 0.0
In [41]:
#set plot parameters
plt.rcParams.update({'font.size': 16})
In [46]:
plt.plot(recycled_by_mat_tonnes.loc[2024:2030,'glass':'backsheet'])
plt.ylim(0,10000)
plt.xlim(2024,2030)
plt.title('Annual End of Life Module Materials:\nc-Si Low Recycling')
plt.ylabel('EoL Module Materials\n[metric tonnes]')
plt.legend(recycled_by_mat_tonnes.columns, fontsize=14)
plt.grid()
In [47]:
plt.plot(recycled_by_mat_tonnes.loc[2024:2030,].filter(like='_hiR'))
plt.ylim(0,10000)
plt.xlim(2024,2030)
plt.title('Annual End of Life Module Materials:\nc-Si High Recycling')
plt.ylabel('EoL Module Materials\n[metric tonnes]')
plt.legend(recycled_by_mat_tonnes.columns, fontsize=14)
plt.grid()
In [48]:
plt.plot(recycled_by_mat_tonnes.loc[2024:2030,'glass_cdte':])
plt.ylim(0,2000)
plt.xlim(2024,2030)
plt.title('Annual End of Life Module Materials:\nCdTe')
plt.ylabel('EoL Module Materials\n[metric tonnes]')
plt.legend(recycled_by_mat_tonnes.loc[2024:2030,'glass_cdte':].columns, fontsize=14)
plt.grid()
In [49]:
recycled_by_mat_tonnes.loc[2024:2030,].sum(axis=0)
Out[49]:
glass                      292.497465
aluminium_frames           578.218934
silver                       0.000000
silicon                      0.000000
copper                       0.000000
encapsulant                  0.000000
backsheet                    0.000000
glass_hiR                27083.098630
aluminium_frames_hiR      7227.736673
silver_hiR                 209.648577
silicon_hiR               3061.331765
copper_hiR                  43.159248
encapsulant_hiR              0.000000
backsheet_hiR                0.000000
glass_cdte                2012.421462
aluminium_frames_cdte        1.465860
copper_cdte                 50.154305
encapsulant_cdte             0.000000
cadmium                      5.360127
tellurium                    5.764160
dtype: float64
In [ ]: